<!--
  - @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com>
  - @author Georg Ehrke <oc.list@georgehrke.com>
  -
  - @license GNU AGPL version 3 or any later version
  -
  - This program is free software: you can redistribute it and/or modify
  - it under the terms of the GNU Affero General Public License as
  - published by the Free Software Foundation, either version 3 of the
  - License, or (at your option) any later version.
  -
  - This program is distributed in the hope that it will be useful,
  - but WITHOUT ANY WARRANTY; without even the implied warranty of
  - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  - GNU Affero General Public License for more details.
  -
  - You should have received a copy of the GNU Affero General Public License
  - along with this program. If not, see <http://www.gnu.org/licenses/>.
  -
  -->

<template>
	<li v-if="showProgressBar" class="settings-fieldset-interior-item">
		<progress
			class="settings-fieldset-interior-item__progressbar"
			:value="imported"
			:max="total" />
	</li>
	<li v-else class="settings-fieldset-interior-item">
		<label class="settings-fieldset-interior-item__import-button button icon icon-upload" :for="inputUid">
			{{ $n('calendar', 'Import calendar', 'Import calendars', 1) }}
		</label>
		<input
			:id="inputUid"
			ref="importInput"
			class="hidden"
			type="file"
			:accept="supportedFileTypes"
			:disabled="disableImport"
			multiple
			@change="processFiles">

		<ImportScreen
			v-if="showImportModal"
			:files="files"
			@cancel-import="cancelImport"
			@import-calendar="importCalendar" />
	</li>
</template>

<script>
import {
	mapState,
} from 'vuex'
import { getParserManager } from 'calendar-js'
import ImportScreen from './ImportScreen.vue'
import { readFileAsText } from '../../../services/readFileAsTextService.js'
import {
	showSuccess,
	showWarning,
	showError,
} from '@nextcloud/dialogs'
import {
	IMPORT_STAGE_AWAITING_USER_SELECT,
	IMPORT_STAGE_DEFAULT,
	IMPORT_STAGE_IMPORTING,
	IMPORT_STAGE_PROCESSING,
} from '../../../models/consts.js'

export default {
	name: 'SettingsImportSection',
	components: {
		ImportScreen,
	},
	props: {
		isDisabled: {
			type: Boolean,
			required: true,
		},
	},
	computed: {
		...mapState({
			files: state => state.importFiles.importFiles,
			stage: state => state.importState.stage,
			total: state => state.importState.total,
			accepted: state => state.importState.accepted,
			denied: state => state.importState.denied,
		}),
		/**
		 * Total amount of processed calendar-objects, either accepted or failed
		 *
		 * @returns {Number}
		 */
		imported() {
			return this.accepted + this.denied
		},
		/**
		 * Whether or not to display the upload button
		 *
		 * @returns {Boolean}
		 */
		allowUploadOfFiles() {
			return this.stage === IMPORT_STAGE_DEFAULT
		},
		/**
		 * Whether or not to display the import modal
		 *
		 * @returns {Boolean}
		 */
		showImportModal() {
			return this.stage === IMPORT_STAGE_AWAITING_USER_SELECT
		},
		/**
		 * Whether or not to display progress bar
		 *
		 * @returns {Boolean}
		 */
		showProgressBar() {
			return this.stage === IMPORT_STAGE_IMPORTING
		},
		/**
		 * Unique identifier for the input field.
		 * Needed for the label
		 *
		 * @returns {String}
		 */
		inputUid() {
			return this._uid + '-import-input'
		},
		/**
		 * Get a list of supported file-types for the file-picker
		 *
		 * This list comes straight from calendar-js.
		 * So in case we add new supported file-types there,
		 * we don't have to change anything here
		 *
		 * @returns {String[]}
		 */
		supportedFileTypes() {
			return getParserManager().getAllSupportedFileTypes()
		},
		/**
		 * Whether or not the import button is disabled
		 *
		 * @returns {Boolean}
		 */
		disableImport() {
			return this.isDisabled || !this.allowUploadOfFiles
		},
	},
	methods: {
		/**
		 * Process all files submitted from the user
		 *
		 * @param {Event} event The change-event of the input-field
		 */
		async processFiles(event) {
			this.$store.commit('changeStage', IMPORT_STAGE_PROCESSING)
			let addedFiles = false

			for (const file of event.target.files) {
				const contents = await readFileAsText(file)
				const lastModified = file.lastModified
				const name = file.name
				const size = file.size
				let type = file.type

				// Handle cases where we are running inside a browser on Windows
				//
				// https://developer.mozilla.org/en-US/docs/Web/API/File/type
				// "Uncommon" file-extensions will result in an empty type
				// and apparently Microsoft considers calendar files to be "uncommon"
				if (type === '') {
					// If it's an xml file, our best guess is xCal: https://tools.ietf.org/html/rfc6321
					// If it's a json file, our best guess is jCal: https://tools.ietf.org/html/rfc7265
					// In every other case, our best guess is just plain old iCalendar: https://tools.ietf.org/html/rfc5545
					if (name.endsWith('.xml')) {
						type = 'application/calendar+xml'
					} else if (name.endsWith('.json')) {
						type = 'application/calendar+json'
					} else if (name.endsWith('.csv')) {
						type = 'text/csv'
					} else {
						type = 'text/calendar'
					}
				}

				// Make sure the user didn't select
				// files of a different file-type
				if (!this.supportedFileTypes.includes(type)) {
					showError(this.$t('calendar', '{filename} is an unsupported file-type', {
						filename: name,
					}))
					continue
				}

				// Use custom-options for parser.
				// The last one in particular will prevent thousands
				// of invitation emails to be sent out on import
				const parser = getParserManager().getParserForFileType(type, {
					extractGlobalProperties: true,
					includeTimezones: true,
					removeRSVPForAttendees: true,
				})

				try {
					parser.parse(contents)
				} catch (error) {
					console.error(error)
					showError(this.$t('calendar', '{filename} could not be parsed', {
						filename: name,
					}))
					continue
				}

				this.$store.commit('addFile', {
					contents,
					lastModified,
					name,
					size,
					type,
					parser,
				})
				addedFiles = true
			}

			if (!addedFiles) {
				showError(this.$t('calendar', 'No valid files found, aborting import'))
				this.$store.commit('removeAllFiles')
				this.$store.commit('resetState')
				return
			}

			this.$store.commit('changeStage', IMPORT_STAGE_AWAITING_USER_SELECT)
		},
		/**
		 * Import all events into the calendars
		 * This will show
		 */
		async importCalendar() {
			await this.$store.dispatch('importEventsIntoCalendar')

			if (this.total === this.accepted) {
				showSuccess(this.$n('calendar', 'Successfully imported %n event', 'Successfully imported %n events.', this.total))
			} else {
				showWarning(this.$t('calendar', 'Import partially failed. Imported {accepted} out of {total}.', {
					accepted: this.accepted,
					total: this.total,
				}))
			}
			this.$store.commit('removeAllFiles')
			this.$store.commit('resetState')

			// Once we are done importing, reload the calendar view
			this.$store.commit('incrementModificationCount')

			this.resetInput()
		},
		/**
		 * Resets the import sate
		 */
		cancelImport() {
			this.$store.commit('removeAllFiles')
			this.$store.commit('resetState')
			this.resetInput()
		},
		/**
		 * Manually reset the file-input, because when you try to upload
		 * the exact same files again, it won't trigger the change event
		 */
		resetInput() {
			this.$refs.importInput.value = ''
		},
	},
}
</script>
